package com.bree.erp.service.stateless;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.bree.erp.entity.GroupType;
import com.bree.erp.entity.ItemRequest;
import com.bree.erp.entity.PartUnit;
import com.bree.erp.entity.Permission;
import com.bree.erp.entity.PermissionId;
import com.bree.erp.entity.PoAdjustment;
import com.bree.erp.entity.PoItem;
import com.bree.erp.entity.PrefixSequence;
import com.bree.erp.entity.Project;
import com.bree.erp.entity.PurchaseOrder;
import com.bree.erp.entity.PurchaseRequisition;
import com.bree.erp.entity.Returns;
import com.bree.erp.entity.Status;
import com.bree.erp.entity.Supplier;
import com.bree.erp.entity.User;
import com.bree.erp.entity.Warehouse;
import com.bree.erp.service.PermissionService;
import com.bree.erp.service.PoAdjustmentService;
import com.bree.erp.service.PoItemService;
import com.bree.erp.service.PrefixSequenceService;
import com.bree.erp.service.PurchaseOrderService;
import com.bree.erp.service.PurchaseRequisitionService;

@Stateless
public class PurchaseOrderServiceBean implements PurchaseOrderService {

	private static Logger LOG = Logger
			.getLogger(PurchaseOrderServiceBean.class);

	@PersistenceContext()
	private EntityManager em;

	@EJB
	private PoItemService poItemService;

	@EJB
	private PoAdjustmentService poAdjustmentService;

	@EJB
	private PermissionService permissionService;

	@EJB
	private PoItemService piService;

	@EJB
	private PurchaseRequisitionService prService;

	@EJB 
	private PrefixSequenceService sequenceService;

	public PurchaseOrderServiceBean() {

	}

	public PurchaseOrderServiceBean(EntityManager em) {
		this.em = em;
	}

	@Override
	public PurchaseOrder create(Long prId, Long supplierId, String userId) {

		PurchaseOrder order = new PurchaseOrder();

		PurchaseRequisition pr = em.find(PurchaseRequisition.class, prId);
		Supplier supplier = em.find(Supplier.class, supplierId);
		User user = em.find(User.class, userId);

		order.setPurchaseRequisition(pr);
		order.setSupplier(supplier);
		order.setStatus(Status.OPEN);

		order.auditCreate(user);
		order.setOwner(user);

		em.persist(order);

		return order;
	}

	@Override
	public PurchaseOrder read(Long id, boolean refresh) {

		PurchaseOrder entity = em.find(PurchaseOrder.class, id);
		if (entity != null) {
			if (refresh == true) {
				em.refresh(entity);
				LOG.debug("id: " + id + "; size: " + entity.getItems().size());
			}

			if (Status.PARTIAL.equals(entity.getStatus())) {
				entity.getInventoryFlows(); // fetch-lazy
			}

		}

		return entity;
	}

	@Override
	public PurchaseOrder read(Long id) {

		return read(id, false);
	}

	@Override
	public PurchaseOrder read(Long prId, Long supplierId, Status status) {

		PurchaseRequisition purchaseRequisition = em.find(
				PurchaseRequisition.class, prId);
		Supplier supplier = em.find(Supplier.class, supplierId);

		TypedQuery<PurchaseOrder> query = em
				.createNamedQuery(PurchaseOrder.QUERY_SELECT_PURCHASEORDER,
						PurchaseOrder.class)
				.setParameter("purchaseRequisition", purchaseRequisition)
				.setParameter("supplier", supplier)
				.setParameter("status", status);

		try {
			return query.getSingleResult();
		} catch (NoResultException e) {
			return null;
		}
	}

	@Override
	public void delete(Long id) {
		PurchaseOrder entity = read(id);

		for (PoAdjustment adjustment : entity.getAdjustments()) {
			em.remove(adjustment);
		}

		entity.getPurchaseRequisition().getOrders().remove(entity);

		em.remove(entity);
	}

	@Override
	public Collection<PurchaseOrder> list() {

		TypedQuery<PurchaseOrder> query = em.createNamedQuery(
				PurchaseOrder.QUERY_ALL_PURCHASEORDER, PurchaseOrder.class);
		Collection<PurchaseOrder> list = query.getResultList();

		LOG.debug("PO size: " + list.size());

		return list;
	}

	@Override
	public PurchaseOrder update(Long id, String warehouseId, String term,
			Date dueDate, List<PoItem> deletePoItems,
			List<PoAdjustment> deleteAdjustments,
			List<PoAdjustment> adjustments, Status status, 
			String remarks, String userId) {

		PurchaseOrder purchaseOrder = read(id);

		Warehouse warehouse = em.find(Warehouse.class, warehouseId);
		User user = em.find(User.class, userId);

		purchaseOrder.setWarehouse(warehouse);
		purchaseOrder.setTerms(term);
		purchaseOrder.setDueDate(dueDate);
		purchaseOrder.setRemarks(remarks);

		// handle PO items tagged for deletion
		if (deletePoItems != null) {
			for (PoItem poItem : deletePoItems) {
				poItemService.delete(poItem.getId());
			}
		}

		// handle PO adjustments tagged for deletion
		if (deleteAdjustments != null) {
			for (PoAdjustment adjustment : deleteAdjustments) {
				poAdjustmentService.delete(adjustment.getId());
			}
		}

		// handle new adjustments
		if (adjustments != null) {
			for (PoAdjustment adjustment : adjustments) {
				if (adjustment.getId() < 0) {
					poAdjustmentService.create(id, adjustment.getDescription(),
							adjustment.getAmount());
				}
			}
		}

		if (status != null) {
			purchaseOrder.setStatus(status);
		}

		purchaseOrder.auditUpdate(user);

		return purchaseOrder;
	}

	@Override
	public PurchaseOrder update(Long poId, Status status, String userId) {

		PurchaseOrder po = read(poId);
		User user = em.find(User.class, userId);

		po.setStatus(status);
		po.auditUpdate(user);

		if (Status.APPROVED.equals(status)) {
			// additional handling for approved.
			updateForApproved(po, user);
		}

		return po;

	}

	private void updateForApproved(PurchaseOrder po, User user) {

		String userId = user.getEmail();
		
		// set order date
		po.setOrderDate(new Date());
		po.setApprovedBy(user);
		po.setApproved(new Date());
		

		// create PR number
		if(po.getPoNumber() == null) {
			String prefix = createPrefix(po);
			PrefixSequence prnum = sequenceService.create(prefix);
			po.setPoNumber(prnum);
		}
		

		// check parent purchase requisition for status change.
		PurchaseRequisition parent = po.getPurchaseRequisition();
		if (parent != null) {
			// check outstanding balance.
			Map<PartUnit, Double> mapQty = piService.totalQuantity(parent
					.getId());
			boolean changeStat = true;
			for (ItemRequest item : parent.getRequests()) {
				Double ordered = mapQty.get(item.getPartUnit());
				if (ordered == null || ordered != item.getQuantity()) {
					changeStat = false;
					break;
				}
			}

			if (changeStat && !parent.isHasOpenOrders()
					&& !parent.isHasPendingOrders()) {

				LOG.debug("PR#" + parent.getId() + " status '"
						+ parent.getStatus() + "' --> '" + Status.CONFIRMED
						+ "'");

				prService.confirm(parent.getId(), userId);
			}
		}

	}
	

	private String createPrefix(PurchaseOrder entity) {
		
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(new Date());
		String year = entity.getPurchaseRequisition().getProject().getCode() + calendar.get(Calendar.YEAR) + "";
		
		String prefix = year.substring(2); 
		
		return prefix;
	}


	@Override
	public Collection<PurchaseOrder> list(Status... statusArr) {

		List<PurchaseOrder> resultList = new ArrayList<PurchaseOrder>();

		for (Status status : statusArr) {

			TypedQuery<PurchaseOrder> query = em.createNamedQuery(
					PurchaseOrder.QUERY_SELECT_PURCHASEORDER_STATUS,
					PurchaseOrder.class).setParameter("status", status);
			
			resultList.addAll(query.getResultList());
		}

		return resultList;
	}

	@Override
	public void withdraw(Warehouse warehouse, Long projectId,
			String description, List<Returns> list, User loggedInUser) {
		
		PurchaseOrder order = new PurchaseOrder();
		Date today = new Date();
		
		order.setWarehouse(warehouse);
		order.setTerms(description);
		order.setOrderDate(today);
		order.setDueDate(today);
		order.auditCreate(loggedInUser);
		order.setStatus(Status.WITHDRAW);
		order.setItems(new ArrayList<PoItem>());
		
		em.persist(order);
		
		for(Returns item : list) {
			PoItem poItem = poItemService.create(order.getId(), item.getPartUnit().getId(), 0.0, item.getQuantity());
			order.getItems().add(poItem);
		}
		
		
	}

	@Override
	public PrefixSequence updateSequence(Long id) {
		
		PurchaseOrder entity = read(id);
		
		PrefixSequence poNumber = entity.getPoNumber();
		if(poNumber != null) {
			return sequenceService.update(poNumber.getId());
		}
		
		return null;
	}

	@Override
	public Collection<PurchaseOrder> list(String userId, Long projectId,
			Status status) {

		if (projectId == null || StringUtils.isBlank(userId)) {
			LOG.error("NULL User ID or project ID passed! Returning empty list.");
			return Collections.emptyList();
		}

		Project project = em.find(Project.class, projectId);
		
		PermissionId permId = new PermissionId(userId, projectId);
		Permission permission = permissionService.read(permId );

		boolean isManager = permission != null 
				&& (permission.getGroups().contains(GroupType.MANAGER));
		
		LOG.debug("isManager: " + isManager + "; project: " + projectId + "; status: " + status);
		
		TypedQuery<PurchaseOrder> query;
		
		if(isManager) {
			query = em
					.createNamedQuery(
							PurchaseOrder.QUERY_FIND_PURCHASEORDER_PER_PROJSTATUS,
							PurchaseOrder.class)
					.setParameter("project", project)
					.setParameter("status", status);
		} else {
			
			User user = em.find(User.class, userId);

			query = em
					.createNamedQuery(
							PurchaseRequisition.QUERY_FIND_PURCHASEREQUISTION_PER_USERPROJSTATUS,
							PurchaseOrder.class)
					.setParameter("user", user)
					.setParameter("project", project)
					.setParameter("status", status);
					
		}

		return query.getResultList();
	}
}
